a tool for shared writing and social publishing
1"use client";
2import { ShortcutKey } from "../../../components/Layout";
3import { Media } from "../../../components/Media";
4import { Popover } from "../../../components/Popover";
5import { metaKey } from "src/utils/metaKey";
6import { useEntitySetContext } from "../../../components/EntitySetProvider";
7import { useState } from "react";
8import { ActionButton } from "components/ActionBar/ActionButton";
9import { HelpSmall } from "../../../components/Icons/HelpSmall";
10import { isMac } from "src/utils/isDevice";
11import { useIsMobile } from "src/hooks/isMobile";
12
13export const HelpButton = (props: { noShortcuts?: boolean }) => {
14 let entity_set = useEntitySetContext();
15 let isMobile = useIsMobile();
16
17 return entity_set.permissions.write ? (
18 <Popover
19 side={isMobile ? "top" : "right"}
20 align={isMobile ? "center" : "start"}
21 asChild
22 className="max-w-xs w-full"
23 trigger={<ActionButton icon={<HelpSmall />} label="About" />}
24 >
25 <div className="flex flex-col text-sm gap-2 text-secondary">
26 {/* about links */}
27 <HelpLink text="📖 Leaflet Manual" url="https://about.leaflet.pub" />
28 <HelpLink text="💡 Make with Leaflet" url="https://make.leaflet.pub" />
29 <HelpLink
30 text="✨ Explore Publications"
31 url="https://leaflet.pub/discover"
32 />
33 <HelpLink text="📣 Newsletter" url="https://buttondown.com/leaflet" />
34 {/* contact links */}
35 <div className="columns-2 gap-2">
36 <HelpLink
37 text="🦋 Bluesky"
38 url="https://bsky.app/profile/leaflet.pub"
39 />
40 <HelpLink text="💌 Email" url="mailto:contact@leaflet.pub" />
41 </div>
42 {/* keyboard shortcuts: desktop only */}
43 <Media mobile={false}>
44 {!props.noShortcuts && (
45 <>
46 <hr className="text-border my-1" />
47 <div className="flex flex-col gap-1">
48 <Label>Text Shortcuts</Label>
49 <KeyboardShortcut name="Bold" keys={[metaKey(), "B"]} />
50 <KeyboardShortcut name="Italic" keys={[metaKey(), "I"]} />
51 <KeyboardShortcut name="Underline" keys={[metaKey(), "U"]} />
52 <KeyboardShortcut
53 name="Highlight"
54 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "H"]}
55 />
56 <KeyboardShortcut
57 name="Strikethrough"
58 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "X"]}
59 />
60 <KeyboardShortcut name="Inline Link" keys={[metaKey(), "K"]} />
61 <KeyboardShortcut
62 name="Make Title"
63 keys={[metaKey(), isMac() ? "Opt" : "Alt", "1"]}
64 />
65 <KeyboardShortcut
66 name="Make Heading"
67 keys={[metaKey(), isMac() ? "Opt" : "Alt", "2"]}
68 />
69 <KeyboardShortcut
70 name="Make Subheading"
71 keys={[metaKey(), isMac() ? "Opt" : "Alt", "3"]}
72 />
73 <KeyboardShortcut
74 name="Regular Text"
75 keys={[metaKey(), isMac() ? "Opt" : "Alt", "0"]}
76 />
77 <KeyboardShortcut
78 name="Large Text"
79 keys={[metaKey(), isMac() ? "Opt" : "Alt", "+"]}
80 />
81 <KeyboardShortcut
82 name="Small Text"
83 keys={[metaKey(), isMac() ? "Opt" : "Alt", "-"]}
84 />
85
86 <Label>Block Shortcuts</Label>
87 {/* shift + up/down arrows (or click + drag): select multiple blocks */}
88 <KeyboardShortcut
89 name="Move Block Up"
90 keys={["Shift", metaKey(), "↑"]}
91 />
92 <KeyboardShortcut
93 name="Move Block Down"
94 keys={["Shift", metaKey(), "↓"]}
95 />
96 {/* cmd/ctrl-a: first selects all text in a block; again selects all blocks on page */}
97 {/* cmd/ctrl + up/down arrows: go to beginning / end of doc */}
98
99 <Label>Canvas Shortcuts</Label>
100 <OtherShortcut name="Add Block" description="Double click" />
101 <OtherShortcut name="Select Block" description="Long press" />
102
103 <Label>Outliner Shortcuts</Label>
104 <KeyboardShortcut
105 name="Make List"
106 keys={[metaKey(), isMac() ? "Opt" : "Alt", "L"]}
107 />
108 {/* tab / shift + tab: indent / outdent */}
109 <KeyboardShortcut
110 name="Toggle Checkbox"
111 keys={[metaKey(), "Enter"]}
112 />
113 <KeyboardShortcut
114 name="Toggle Fold"
115 keys={[metaKey(), "Shift", "Enter"]}
116 />
117 <KeyboardShortcut
118 name="Fold All"
119 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↑"]}
120 />
121 <KeyboardShortcut
122 name="Unfold All"
123 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↓"]}
124 />
125 </div>
126 </>
127 )}
128 </Media>
129 {/* links: terms and privacy */}
130 <hr className="text-border my-1" />
131 {/* <HelpLink
132 text="Terms and Privacy Policy"
133 url="https://leaflet.pub/legal"
134 /> */}
135 <div>
136 <a href="https://leaflet.pub/legal" target="_blank">
137 Terms and Privacy Policy
138 </a>
139 </div>
140 </div>
141 </Popover>
142 ) : null;
143};
144
145const KeyboardShortcut = (props: { name: string; keys: string[] }) => {
146 return (
147 <div className="flex gap-2 justify-between items-center">
148 {props.name}
149 <div className="flex gap-1 items-center font-bold">
150 {props.keys.map((key, index) => {
151 return <ShortcutKey key={index}>{key}</ShortcutKey>;
152 })}
153 </div>
154 </div>
155 );
156};
157
158const OtherShortcut = (props: { name: string; description: string }) => {
159 return (
160 <div className="flex justify-between items-center">
161 <span>{props.name}</span>
162 <span>
163 <strong>{props.description}</strong>
164 </span>
165 </div>
166 );
167};
168
169const Label = (props: { children: React.ReactNode }) => {
170 return <div className="text-tertiary font-bold pt-2 ">{props.children}</div>;
171};
172
173const HelpLink = (props: { url: string; text: string }) => {
174 const [isHovered, setIsHovered] = useState(false);
175 const handleMouseEnter = () => {
176 setIsHovered(true);
177 };
178 const handleMouseLeave = () => {
179 setIsHovered(false);
180 };
181 return (
182 <a
183 href={props.url}
184 target="_blank"
185 className="py-2 px-2 rounded-md flex flex-col gap-1 bg-border-light hover:bg-border hover:no-underline"
186 style={{
187 backgroundColor: isHovered
188 ? "rgb(var(--accent-light))"
189 : "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
190 }}
191 onMouseEnter={handleMouseEnter}
192 onMouseLeave={handleMouseLeave}
193 >
194 <strong>{props.text}</strong>
195 </a>
196 );
197};